﻿//-----------------------------------------------------------------------------
// <copyright file="OnyxContainer.cs" company="William E. Kempf">
//     Copyright (c) William E. Kempf.
// </copyright>
//-----------------------------------------------------------------------------

namespace Onyx.Windows
{
    using System;
    using System.Collections.Generic;
#if WPF
    using System.ComponentModel.Design;
#endif
    using Onyx.ComponentModel;
    using Onyx.Reflection;

    /// <summary>
    /// Defines a container for services.
    /// </summary>
    /// <remarks>
    /// Silverlight doesn't provide a ServiceContainer type, so this provides an alternative.
    /// </remarks>
    public class OnyxContainer : IServiceRegistry, IServiceProvider
    {
        /// <summary>
        /// The registered services.
        /// </summary>
        private readonly Dictionary<Type, object> services = new Dictionary<Type, object>();

        /// <summary>
        /// Initializes a new instance of the <see cref="OnyxContainer"/> class.
        /// </summary>
        public OnyxContainer()
        {
        }

        /// <summary>
        /// Gets the service object of the specified type.
        /// </summary>
        /// <param name="serviceType">An object that specifies the type of service object to get.</param>
        /// <returns>
        /// A service object of type <paramref name="serviceType"/>.
        /// -or-
        /// null if there is no service object of type <paramref name="serviceType"/>.
        /// </returns>
        public object GetService(Type serviceType)
        {
            object service;
            if (this.services.TryGetValue(serviceType, out service))
            {
                Func<IServiceProvider, Type, object> callback = service as Func<IServiceProvider, Type, object>;
                if (callback != null)
                {
                    service = callback(this, serviceType);
                    if (((service != null) && !service.GetType().IsCOMObject) && !serviceType.IsAssignableFrom(service.GetType()))
                    {
                        service = null;
                    }

                    this.services[serviceType] = service;
                }

                return service;
            }

            return null;
        }

        /// <summary>
        /// Adds the specified service to the service container.
        /// </summary>
        /// <param name="serviceType">The type of service to add.</param>
        /// <param name="serviceInstance">An instance of the service type to add. This object must implement or inherit from the type indicated by the <paramref name="serviceType"/> parameter.</param>
        public void RegisterService(Type serviceType, object serviceInstance)
        {
            if (serviceType == null)
            {
                throw new ArgumentNullException(Reflect.GetField(() => serviceType).Name);
            }

            this.services[serviceType] = serviceInstance;
        }

        /// <summary>
        /// Adds the specified service to the service container.
        /// </summary>
        /// <param name="serviceType">The type of service to add.</param>
        /// <param name="callback">A callback object that is used to create the service. This allows a service to be declared as available, but delays the creation of the object until the service is requested.</param>
        public void RegisterService(Type serviceType, Func<IServiceProvider, Type, object> callback)
        {
            if (serviceType == null)
            {
                throw new ArgumentNullException(Reflect.GetField(() => serviceType).Name);
            }

            this.services[serviceType] = callback;
        }

        /// <summary>
        /// Removes the specified service type from the service container.
        /// </summary>
        /// <param name="serviceType">The type of service to remove.</param>
        public void RemoveService(Type serviceType)
        {
            if (serviceType == null)
            {
                throw new ArgumentNullException(Reflect.GetField(() => serviceType).Name);
            }

            this.services.Remove(serviceType);
        }

        public void RegisterServicesFor(object serviceProvider)
        {
            RegisterBaseTypes(serviceProvider.GetType(), serviceProvider);
            IRegisterServices registrar = serviceProvider as IRegisterServices;
            if (registrar != null)
            {
                registrar.RegisterServices(this);
            }
        }

        private void RegisterBaseTypes(Type type, object instance)
        {
            if (type == null || type == typeof(object))
            {
                return;
            }

            this.RegisterService(type, instance);
            foreach (Type interfaceType in type.GetInterfaces())
            {
                RegisterBaseTypes(interfaceType, instance);
            }

            RegisterBaseTypes(type.BaseType, instance);
        }

        /// <summary>
        /// Gets a service provider configured with the services registered on this instance.
        /// </summary>
        /// <returns>
        /// An <see cref="IServiceProvider"/> configured with the services registered on this instance.
        /// </returns>
        public IServiceProvider CreateServiceProvider()
        {
            return this;
        }
    }
}
